﻿using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using PesticideManage.API.Data;
using PesticideManage.API.Extensions;
using PesticideManage.API.Services.Identity.UserAggregate;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;

namespace PesticideManage.API.Services.JWT;

public class AuthTokenService : IAuthTokenService
{
    private const string RefreshTokenIdClaimType = "refresh_token_id";

    private readonly JwtBearerOptions _jwtBearerOptions;
    private readonly JwtOptions _jwtOptions;
    private readonly SigningCredentials _signingCredentials;
    private readonly IDistributedCache _distributedCache;
    private readonly PesticideContext _context;
    private readonly ILogger<AuthTokenService> _logger;

    public AuthTokenService(
       IOptionsSnapshot<JwtBearerOptions> jwtBearerOptions,
       IOptionsSnapshot<JwtOptions> jwtOptions,
       SigningCredentials signingCredentials,
       IDistributedCache distributedCache,
       PesticideContext context,
       ILogger<AuthTokenService> logger)
    {
        _jwtBearerOptions = jwtBearerOptions.Get(JwtBearerDefaults.AuthenticationScheme);
        _jwtOptions = jwtOptions.Value;
        _signingCredentials = signingCredentials;
        _distributedCache = distributedCache;
        _context = context;
        _logger = logger;

    }

    public async Task<AuthTokenDTO> CreateAuthTokenAsync(User user)
    {
        if (user is null || user.Id == 0)
        {
            throw new ArgumentNullException(nameof(user));
        }
        var result = new AuthTokenDTO();

        // 先创建refresh token
        var refreshToken = await CreateRefreshTokenAsync(user.Id);
        ArgumentNullException.ThrowIfNull(refreshToken, nameof(refreshToken));
        ArgumentNullException.ThrowIfNull(refreshToken.Id, nameof(refreshToken.Id));
        result.RefreshToken = refreshToken.Token;

        // 再签发Jwt
        result.AccessToken = CreateJwtToken(user, refreshToken.Id);
        result.ExpiresIn = _jwtOptions.AccessTokenExpiresMinutes * 60;
        return result;
    }
    public sealed class RefreshToken
    {
        public string Id { get; set; } = null!;
        public string Token { get; set; } = null!;
    }
    private async Task<RefreshToken> CreateRefreshTokenAsync(int userId)
    {
        // refresh token id作为缓存Key
        string tokenId = Guid.NewGuid().ToString("N");
        // _logger.LogInformation(tokenId);
        // 生成refresh token
        byte[] rnBytes = new byte[32];
        using var rng = RandomNumberGenerator.Create();
        rng.GetBytes(rnBytes);
        string token = Convert.ToBase64String(rnBytes);

        // 设置refresh token的过期时间
        var options = new DistributedCacheEntryOptions();
        options.SetAbsoluteExpiration(TimeSpan.FromDays(_jwtOptions.RefreshTokenExpiresDays));

        // 缓存 refresh token
        await _distributedCache.SetStringAsync(GetRefreshTokenKey(userId, tokenId), token, options);
        var refreshToekn = new RefreshToken { Id = tokenId, Token = token };

        //_context.RefreshTokens.Add(refreshToekn);
        //await _context.SaveChangesAsync();

        return refreshToekn;
    }
    private string CreateJwtToken(User user, string refreshTokenId)
    {
        //Console.WriteLine(JsonSerializer.Serialize(user));
        if (user is null)
            throw new ArgumentNullException(nameof(user), $"{nameof(user)}为空");

        if (string.IsNullOrEmpty(refreshTokenId))
        {
            _logger.LogInformation("令牌为空");
            throw new ArgumentNullException(nameof(refreshTokenId), $"{nameof(refreshTokenId)}为空");
        }


        //ArgumentNullException.ThrowIfNull(user?.Roles);
        var claims = new List<Claim>(15)
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new Claim(ClaimTypes.Name, user.Name),
            // 将 refresh token id 记录下来
            new Claim(RefreshTokenIdClaimType, refreshTokenId)
        };
        if (!string.IsNullOrEmpty(user.MobileNumber))
        {
            claims.Add(
            new Claim(ClaimTypes.MobilePhone, user.MobileNumber)
            );
        }
        //if (user.Roles != null)
        //{
        //    foreach (var role in user.Roles)
        //    {
        //        //var permissions = _context.RolePermissions.Where(rp => rp.RoleId == role.Id).Select(rp => rp.PermissionId).ToArray();
        //        //arr = arr.Union(permissions).ToArray();
        //        claims.Add(new Claim(ClaimTypes.Role, role.Name));
        //    }
        //}


        //if (user?.OpenId is not null)
        //{
        //    claims.Add(new Claim("OpenId", user.OpenId));
        //}



        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(claims),
            Issuer = _jwtBearerOptions.TokenValidationParameters.ValidIssuer,
            Audience = _jwtBearerOptions.TokenValidationParameters.ValidAudience,
            //Expires = DateTime.UtcNow.AddMinutes(_jwtOptions.AccessTokenExpiresMinutes),
            Expires = DateTime.UtcNow.AddMinutes(30),
            SigningCredentials = _signingCredentials,
        };

        var handler = _jwtBearerOptions.SecurityTokenValidators.OfType<JwtSecurityTokenHandler>().FirstOrDefault()
            ?? new JwtSecurityTokenHandler();
        JwtSecurityToken securityToken = handler.CreateJwtSecurityToken(tokenDescriptor);
        string token = handler.WriteToken(securityToken);

        return token;//$"{JwtBearerDefaults.AuthenticationScheme} {token}" ;
    }

    private string GetRefreshTokenKey(int userId, string refreshTokenId)
    {
        if (string.IsNullOrEmpty(refreshTokenId))
            throw new ArgumentNullException(nameof(refreshTokenId));

        return $"{userId}:{refreshTokenId}";
    }
    /// <summary>
    /// 刷新令牌
    /// </summary>
    /// <param name="token"></param>
    /// <returns></returns>
    /// <exception cref="BadHttpRequestException"></exception>
    public async Task<AuthTokenDTO> RefreshAuthTokenAsync(AuthTokenDTO token)
    {
        var validationParameters = _jwtBearerOptions.TokenValidationParameters.Clone();
        // 不校验生命周期
        validationParameters.ValidateLifetime = false;

        var handler = _jwtBearerOptions.SecurityTokenValidators.OfType<JwtSecurityTokenHandler>().FirstOrDefault()
            ?? new JwtSecurityTokenHandler();
        ClaimsPrincipal? user = null;
        try
        {
            // 先验证一下，jwt是否真的有效
            user = handler.ValidateToken(token.AccessToken, validationParameters, out _);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex.ToString());
            throw new BadHttpRequestException("Invalid access token");
        }

        // var identity = user.Identities.First();

        int userId = user.GetId();

        string? refreshTokenId = user.Claims.FirstOrDefault(c => c.Type == RefreshTokenIdClaimType)?.Value;
        ArgumentNullException.ThrowIfNull(refreshTokenId);

        string refreshTokenKey = GetRefreshTokenKey(userId, refreshTokenId);
        string? refreshToken = await _distributedCache.GetStringAsync(refreshTokenKey);

        //if (refreshToken == null)
        //{
        //    var tk = await _context.RefreshTokens.FindAsync(refreshTokenId);
        //    if (tk != null)
        //    {
        //        refreshToken = tk.Token;
        //        _context.RefreshTokens.Remove(tk);
        //        await _context.SaveChangesAsync();
        //    }
        //}
        // 验证refresh token是否有效
        if (refreshToken != token.RefreshToken)
        {
            throw new BadHttpRequestException("登录超时");
        }

        // refresh token用过了记得清除掉
        await _distributedCache.RemoveAsync(refreshTokenKey);

        // 这里应该是从数据库中根据 userId 获取用户信息

        var userEntity = await _context.Users.Where(u => u.Id == userId).Select(u => new User(u.Id) { Name = u.Name, MobileNumber = u.MobileNumber }).FirstOrDefaultAsync();
        ArgumentNullException.ThrowIfNull(userEntity);
        return await CreateAuthTokenAsync(userEntity);
    }
}